package com.cxm.weilai.util;

import org.apache.commons.lang3.StringUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;


/**
 * Socket通讯
 */
public class SocketUtil {
    /**
     * 开始连接，得到Socket对象
     *
     * @param ip             服务器ip
     * @param port           服务器端口
     * @param connectTimeout 连接超时
     * @param readTimeout    读超时 （通讯超时）
     */
    public static Socket connect(String ip, int port, int connectTimeout, int readTimeout) throws Exception {
        Socket socket = new Socket();
        // socket.setReceiveBufferSize(1000000000);
        socket.setReceiveBufferSize(512000);
        socket.connect(new InetSocketAddress(ip, port), connectTimeout);
        socket.setSoTimeout(readTimeout);
        return socket;

    }

    /**
     * 关闭连接
     */
    public static void close(Socket socket) throws Exception {
        if (socket != null) {
            socket.close();
            socket = null;
        }
    }

    /**
     * 读字节流，直到结束
     *
     * @param input 输入流
     * @return 读到的字节
     */
    public static byte[] read(InputStream input) throws Exception {
        byte[] buff = null;
        byte[] result = new byte[0];

        while (true) {
            buff = new byte[1024];
            int read = input.read(buff);

            if (read < 0) {
                break;
            } else if (read == 0) {
                continue;
            }

            byte[] temp = new byte[result.length + read];
            System.arraycopy(result, 0, temp, 0, result.length);
            System.arraycopy(buff, 0, temp, result.length, read);
            result = temp;

        }
        return result;
    }

    /**
     * 读字节流，直到指定的一组字节（字符）或流结束。
     * 例：读取一行HTTP头 <br>
     * InputStream is = 输入流...... <br>
     * byte[] data = read(is, "\r\n".getBytes()); <br>
     * String httpHead = new String(data).trim();
     *
     * @param input    输入流
     * @param endChars 指定的一组字节（字符）
     * @return 读到的字节。包含指定的一组字节（字符），如果有的话。
     */
    public static byte[] read(InputStream input, byte[] endChars) throws Exception {
        if (endChars == null || endChars.length < 1) {
            return read(input);
        }

        byte[] buff = new byte[1024];
        int readCount = 0;

        // 试图匹配的字节在endChars的位置
        int tryMatchPos = 0;

        while (true) {

            int oneByte = input.read();
            if (oneByte < 0) {
                break;
            }

            if (readCount == buff.length) {
                byte[] temp = new byte[buff.length * 2];
                System.arraycopy(buff, 0, temp, 0, readCount);
                buff = temp;
            }
            buff[readCount++] = (byte) oneByte;

            if ((byte) oneByte == endChars[tryMatchPos]) {
                if (tryMatchPos == endChars.length - 1) {
                    // 完整匹配所有endChars中的字节（字符）
                    break;
                } else {
                    // 匹配了一个endChars中的字节（字符）
                    tryMatchPos++;
                }
            } else {
                // 不匹配，重来
                tryMatchPos = 0;
            }

        }

        if (readCount == 0) {
            return new byte[0];
        }

        byte[] result = new byte[readCount];
        System.arraycopy(buff, 0, result, 0, readCount);
        return result;

    }

    /**
     * 读定长字节数，字节数不足则抛出异常
     *
     * @param length 期望得到的字节长度
     * @return 读到的字节
     */
    public static byte[] read(Socket socket, int length) throws Exception {
        InputStream input = socket.getInputStream();
        return read(input, length);
    }

    /**
     * 读定长字节数，字节数不足则抛出异常
     *
     * @param input  输入流
     * @param length 期望得到的字节长度
     * @return 读到的字节
     */
    public static byte[] read(InputStream input, int length) throws Exception {
        byte[] buff = null;
        byte[] result = new byte[length];
        int remain = length;

        while (remain > 0) {
            buff = new byte[remain];
            int read = input.read(buff);

            if (read < 0) {
                throw new Exception("读到输入流结束,字节数不足");
            }
            System.arraycopy(buff, 0, result, length - remain, read);
            remain -= read;
        }
        return result;

    }

    /**
     * 按HTTP的chunked方式读取
     */
    public static byte[] readHttpChunked(Socket socket) throws Exception {
        InputStream input = socket.getInputStream();
        return readHttpChunked(input);
    }

    /**
     * 按HTTP的chunked方式读取
     *
     * @param input 输入流
     */
    public static byte[] readHttpChunked(InputStream input) throws Exception {
        byte[] httpBody = new byte[0];
        byte[] endChars = "\r\n".getBytes();
        byte[] tempNew = new byte[0];
        byte[] temp2Old = new byte[0];

        while (true) {
            byte[] data = read(input, endChars);
            String chunkSizeLine = new String(data).trim();
            String[] arrChunkSizeLine = StringUtils.split(chunkSizeLine, ";");
            int chunkSize = Integer.parseInt(arrChunkSizeLine[0], 16);

            // 是最后一个 chunk
            if (chunkSize == 0) {
                data = read(input, endChars);
                if (data.length <= 2) {
                    // 没有trailer，本行是结束行
                } else {
                    // 本行是trailer，需再读结束行
                    read(input, endChars);
                }
                break;
            }

            byte[] buff = read(input, chunkSize);
            httpBody = new byte[chunkSize];
            System.arraycopy(buff, 0, httpBody, 0, chunkSize);
            temp2Old = new byte[httpBody.length + tempNew.length];
            System.arraycopy(tempNew, 0, temp2Old, 0, tempNew.length);
            System.arraycopy(httpBody, 0, temp2Old, tempNew.length, httpBody.length);
            tempNew = temp2Old;

            // 读chunk的结尾标志：回车换行
            read(input, endChars);
        }
        httpBody = tempNew;
        return httpBody;

    }

    /**
     * 写字节流（发送）
     *
     * @param data 字节数据
     */
    public static void write(Socket socket, byte[] data) throws Exception {
        OutputStream output = socket.getOutputStream();
        write(output, data);
    }

    /**
     * 写字节流（发送）
     *
     * @param output 输出流
     * @param data   字节数据
     */
    public static void write(OutputStream output, byte[] data) throws Exception {
        output.write(data);
        output.flush();
    }

}
